﻿using System;
using System.Collections.Generic;
using GeoAPI.Geometries;
using YJKGridTopologyCal.Algorithm;
using YJKGridTopologyCal.GeometriesGraph;
using YJKGridTopologyCal.Utilities;

namespace YJKGridTopologyCal.Operation.Overlay
{
	public class LineBuilder
	{
		public LineBuilder(OverlayOp op, IGeometryFactory geometryFactory, PointLocator ptLocator)
		{
			this._op = op;
			this._geometryFactory = geometryFactory;
			this._ptLocator = ptLocator;
		}

		public IList<IGeometry> Build(SpatialFunction opCode)
		{
			this.FindCoveredLineEdges();
			this.CollectLines(opCode);
			this.BuildLines(opCode);
			return this._resultLineList;
		}

		private void FindCoveredLineEdges()
		{
			foreach (Node node in this._op.Graph.Nodes)
			{
				((DirectedEdgeStar)node.Edges).FindCoveredLineEdges();
			}
			foreach (EdgeEnd edgeEnd in this._op.Graph.EdgeEnds)
			{
				DirectedEdge directedEdge = (DirectedEdge)edgeEnd;
				Edge edge = directedEdge.Edge;
				if (directedEdge.IsLineEdge && !edge.IsCoveredSet)
				{
					bool covered = this._op.IsCoveredByA(directedEdge.Coordinate);
					edge.Covered = covered;
				}
			}
		}

		private void CollectLines(SpatialFunction opCode)
		{
			foreach (EdgeEnd edgeEnd in this._op.Graph.EdgeEnds)
			{
				DirectedEdge de = (DirectedEdge)edgeEnd;
				this.CollectLineEdge(de, opCode, this._lineEdgesList);
				this.CollectBoundaryTouchEdge(de, opCode, this._lineEdgesList);
			}
		}

		public void CollectLineEdge(DirectedEdge de, SpatialFunction opCode, IList<Edge> edges)
		{
			Label label = de.Label;
			Edge edge = de.Edge;
			if (de.IsLineEdge && !de.IsVisited && OverlayOp.IsResultOfOp(label, opCode) && !edge.IsCovered)
			{
				edges.Add(edge);
				de.VisitedEdge = true;
			}
		}

		public void CollectBoundaryTouchEdge(DirectedEdge de, SpatialFunction opCode, IList<Edge> edges)
		{
			Label label = de.Label;
			if (de.IsLineEdge)
			{
				return;
			}
			if (de.IsVisited)
			{
				return;
			}
			if (de.IsInteriorAreaEdge)
			{
				return;
			}
			if (de.Edge.IsInResult)
			{
				return;
			}
			Assert.IsTrue((!de.IsInResult && !de.Sym.IsInResult) || !de.Edge.IsInResult);
			if (OverlayOp.IsResultOfOp(label, opCode) && opCode == SpatialFunction.Intersection)
			{
				edges.Add(de.Edge);
				de.VisitedEdge = true;
			}
		}

		private void BuildLines(SpatialFunction opCode)
		{
			foreach (Edge edge in this._lineEdgesList)
			{
				ILineString item = this._geometryFactory.CreateLineString(edge.Coordinates);
				this._resultLineList.Add(item);
				edge.InResult = true;
			}
		}

		private void LabelIsolatedLines(IEnumerable<Edge> edgesList)
		{
			foreach (Edge edge in edgesList)
			{
				Label label = edge.Label;
				if (edge.IsIsolated)
				{
					if (label.IsNull(0))
					{
						this.LabelIsolatedLine(edge, 0);
					}
					else
					{
						this.LabelIsolatedLine(edge, 1);
					}
				}
			}
		}

		private void LabelIsolatedLine(Edge e, int targetIndex)
		{
			Location location = this._ptLocator.Locate(e.Coordinate, this._op.GetArgGeometry(targetIndex));
			e.Label.SetLocation(targetIndex, location);
		}

		private readonly OverlayOp _op;

		private readonly IGeometryFactory _geometryFactory;

		private readonly PointLocator _ptLocator;

		private readonly List<Edge> _lineEdgesList = new List<Edge>();

		private readonly List<IGeometry> _resultLineList = new List<IGeometry>();
	}
}
